package wsi.depend;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.rygh.core.model.common.MapList;
import org.rygh.core.model.common.MapSet;
import org.rygh.core.util.UIo;
import org.rygh.core.util.counter.FrequencyCounter;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.gui.swing.input.Input;

import wsi.dataguide.tree.WSXmlLeaf;
import wsi.dataguide.tree.WSXmlParser;
import wsi.dataguide.tree.WSXmlTree;
import wsi.depend.model.Dependency;
import wsi.detect.DetectHelper;
import wsi.detect.InputDetector;
import wsi.exp.Exp2TreeToEntity;
import wsi.function.DirManager;
import wsi.function.Function;
import wsi.match.MatchProperty;
import wsi.match.MatchProperty.MatchType;
import wsi.match.MatchProperty.Threshold;
import wsi.match.XmlFunctionality;
import wsi.match.XmlFunctionalityController;

public class DependencyDetector {
	private final static double FUNCTIONALITY_THRESHOLD = 0.8;
	private final static int TEST_QUOTA_PER_INPUT = 5;
	private final static int INPUT_QUOTA = 5;

	private final FrequencyCounter<XmlPath> invokeCounter = new FrequencyCounter<>();
	private final Function dependant;

	private Function provider;
	private Dependency dependency;

	/**
	 * we assume that dependant is correctly defined.
	 * 
	 * @param dependant
	 */
	public DependencyDetector(Function dependant) {
		this.dependant = dependant;
	}

	/**
	 * we try each leaf in the descending order w.r.t. comp. inv. func. This
	 * should be called after the potential dependent function is computed.<br>
	 * 
	 * we assume there will be one dependency only, need to check multiple
	 * dependency case. (dependent on different leaves).<br>
	 * 
	 * for all extracted data from xml trees, we do not try to call all of them
	 * because (1) the number is too large (can be over a hundred); (2) calling
	 * ws is time-consuming; (3) ws will ban if call too many times. we will
	 * randomly pick around 10 to test if they are good candidates.<br>
	 * 
	 * 13-11-14 the idea is to use input detector to help detect here. thus we
	 * need to prepare full information for an input (at least the entity,
	 * because we need to extract the entity tree).<br>
	 * 
	 * @return whether dependant is dependent on provider.
	 */
	public boolean detect() {
		System.out.printf("\n[info] check if %s depends on %s.\n",
				dependant.getName(), provider.getName());

		dependency = null;

		List<MatchProperty> goldMatches = Exp2TreeToEntity
				.getFunctionMatchProperty(provider);

		if (!Exp2TreeToEntity
				.isFunctionCorrectlyDetected(provider, goldMatches))
			return false;

		MatchProperty matchProperty = goldMatches.get(0);

		// if (matchProperty.isExistsMatch())
		// return false;

		/*
		 * we should load inputs here and they are expected to appear (as
		 * provider should be correctly mapped).
		 */

		DirManager prov_dm = new DirManager(provider);
		prov_dm.loadInputs();

		if (prov_dm.getInputs() == null) {
			System.out.printf("[error] load inputs failed for %s\n",
					provider.getName());
			return false;
		}

		XmlFunctionalityController xmlFuncCtrl;
		xmlFuncCtrl = new XmlFunctionalityController(prov_dm);

		if (!xmlFuncCtrl.load()) {
			System.out.println("[error] failed to load xml functionality.");
			return false;
		}

		List<XmlFunctionality> sortedFunctionalities = xmlFuncCtrl
				.getSortedList();

		//

		InputDetector detector = new InputDetector(dependant);

		DetectHelper helper = detector.getDetectHelper();
		helper.setCacheWhatever(true);
		helper.setSaveInputFile(false);

		double ratio, partial_max_ratio;
		int c;

		for (XmlFunctionality func : sortedFunctionalities) {
			double cifunc = func.getCompleteInverseFunctionality();

			if (cifunc < FUNCTIONALITY_THRESHOLD)
				continue;

			XmlPath xpath = func.getXpath();
			MapList<Input, Input> mapList = getValueMap(prov_dm, xpath,
					matchProperty.getType());

			c = 0;
			partial_max_ratio = -1;
			detector.clear();

			for (Entry<Input, List<Input>> e : mapList.entrySet()) {
				c++;

				Input[] new_vals = e.getValue().toArray(new Input[0]);

				if (new_vals.length > 1)
					System.out.printf("input [%s] size %d at %s\n", e.getKey(),
							new_vals.length, xpath);

				// such that we can cache.

				if (new_vals.length > 1)
					Arrays.sort(new_vals, new Comparator<Input>() {
						@Override
						public int compare(Input o1, Input o2) {
							return o1.getValue().compareTo(o2.getValue());
						}
					});

				int l = Math.min(TEST_QUOTA_PER_INPUT, new_vals.length);

				for (int i = 0; i < l; i++) {
					Input new_val = new_vals[i];

					detector.detect(new_val);
				}

				ratio = 1. * detector.inputSize() / detector.getWebcalls();

				if (partial_max_ratio < ratio)
					partial_max_ratio = ratio;

				/*
				 * if for the first INPUT_QUOTA inputs, the ratio is very low,
				 * then we skip.
				 */

				// System.out.printf("[info] %s (%.2f), ratio %.2f, input %s\n",
				// xpath, cifunc, ratio, e.getKey());

				if (c >= INPUT_QUOTA) {
					if (partial_max_ratio < Threshold.MINORITY) {
						System.out
								.printf("[info] skip bad, partial max %.2f @ %s (%.2f).\n",
										partial_max_ratio, xpath, cifunc);
						break;
					}
				}
			}

			invokeCounter.set(xpath, null, detector.inputSize(),
					detector.getWebcalls());

			/*
			 * if we already find a good one, break.
			 */

			ratio = 1. * detector.inputSize() / detector.getWebcalls();

			if (ratio > Threshold.MAJORITY) {
				System.out.printf("[info] find one %s good enough (%.2f)\n",
						xpath, ratio);
				break;
			}

		}

		ratio = invokeCounter.getMaxRatio();

		boolean depend = ratio > Threshold.MIDDLE;

		if (depend) {
			dependency = new Dependency(dependant, provider);
			dependency.setXpath(invokeCounter.getMaxRatioObject());

			// System.out.printf("invokeCounter:\n\t%s",
			// invokeCounter.toString());

			System.out.printf("[info] success, ratio %.2f.\n", ratio);
		} else
			System.out.printf("[info] failed,ratio %.2f.\n", ratio);

		return depend;
	}

	public Function getDependant() {
		return dependant;
	}

	public Dependency getDependency() {
		return dependency;
	}

	public Function getProvider() {
		return provider;
	}

	@Deprecated
	MapSet<XmlPath, String> getValueMap(DirManager providerManager,
			List<XmlFunctionality> sortedFunctionalities,
			List<XmlFunctionality> sortedList) {
		MapSet<XmlPath, String> valueMap = new MapSet<>();

		XmlPath xpath;

		Map<Input, File> fileMap = providerManager.getFileMap();
		List<WSXmlTree> trees = new ArrayList<>();
		WSXmlTree xmlTree;

		for (Entry<Input, File> e : fileMap.entrySet()) {
			File f = e.getValue();

			String content = UIo.readFile(f);
			WSXmlParser parser = new WSXmlParser(e.getKey(), content, true);
			xmlTree = parser.parse();

			if (parser.isValid())
				trees.add(xmlTree);
		}

		for (XmlFunctionality f : sortedFunctionalities) {
			if (f.getCompleteInverseFunctionality() < FUNCTIONALITY_THRESHOLD)
				continue;

			xpath = f.getXpath();
			sortedList.add(f);

			for (WSXmlTree t : trees) {
				List<WSXmlLeaf> nodes = t.getNodesByXPath(xpath);

				if (nodes.isEmpty()) {
					System.out.printf("[warn] no iso-subtrees for %s at %s\n",
							t.getName(), xpath);
					continue;
				}

				for (WSXmlLeaf n : nodes)
					valueMap.put(xpath, n.getValue().getValue());
			}
		}

		return valueMap;
	}

	/**
	 * key is from provider function (getArtistByName), value is a composed
	 * input (Entity, pred, Value) where entity is from input and value is the
	 * actual value extracted from XML. we need the entity value to extract
	 * subgraphs.
	 * 
	 * @param providerManager
	 * @param xpath
	 * @param matchType
	 * @return
	 */
	private MapList<Input, Input> getValueMap(DirManager providerManager,
			XmlPath xpath, MatchType matchType) {
		/*
		 * we cannot use mapset here. for example, try to check if
		 * getArtistInfoById depends on getReleasesByArtistName. different
		 * release has different id. but there is one master artist. if we use
		 * set here, all of them will be "condensed" to one. thus we have to use
		 * list here.
		 */
		MapList<Input, Input> valueMap = new MapList<>();

		Map<Input, File> fileMap = providerManager.getFileMap();
		Map<Input, WSXmlTree> treeMap = new HashMap<>();
		WSXmlTree xmlTree;
		String value;
		Input composed_input, input;

		for (Entry<Input, File> e : fileMap.entrySet()) {
			input = e.getKey();
			File f = e.getValue();

			String content = UIo.readFile(f);
			WSXmlParser parser = new WSXmlParser(e.getKey(), content, true);
			xmlTree = parser.parse();

			if (parser.isValid())
				treeMap.put(input, xmlTree);
		}

		for (Entry<Input, WSXmlTree> e : treeMap.entrySet()) {
			input = e.getKey();
			WSXmlTree tree = e.getValue();

			List<WSXmlLeaf> nodes = tree.getNodesByXPath(xpath);

			if (nodes.isEmpty()) {
				System.out.printf("[warn] no iso-subtrees for %s at %s\n",
						tree.getName(), xpath);
				continue;
			}

			if (matchType == MatchType.EXISTS) {
				value = nodes.get(0).getValue().getValue();
				composed_input = new Input(input.getEntity(), null, value);

				valueMap.put(input, composed_input);
			} else {
				for (WSXmlLeaf n : nodes) {
					value = n.getValue().getValue();
					composed_input = new Input(input.getEntity(), null, value);

					valueMap.put(input, composed_input);
				}
			}
		}

		return valueMap;
	}

	public void reset() {
		invokeCounter.clear();
		dependency = null;
	}

	public void setProvider(Function provider) {
		this.provider = provider;
	}

}
